﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.ManagementConsole;
using System.ComponentModel;
using System.Timers;

namespace SharpShare.Management.Mmc.Views {
    /// <summary>
    /// Uses type descriptor info to populate a list view.
    /// </summary>
    /// <remarks>
    /// The list view will populate the columns for the scope node when the ExcludesScopeNodes is NOT set.  It finds the appropriate scope node
    /// by checking the scope node's tag == the report object.
    /// </remarks>
    public abstract class TypeDescriptorReportListView<T> : MmcListView {
        private Timer _updateTimer;

        protected TypeDescriptorReportListView() {

        }

        private PropertyDescriptorCollection Properties { get; set; }

        protected virtual bool ShouldRefreshPeriodically {
            get {
                return false;
            }
        }

        protected abstract IEnumerable<T> ListReportObjects();

        protected virtual IEqualityComparer<T> ReportObjectComparer {
            get {
                return EqualityComparer<T>.Default;
            }
        }

        protected virtual ResultNode CreateResultNode(T reportObject) {
            return new ResultNode();
        }

        protected virtual void PopulateResultNode(Node node, T reportObject) {
            node.Tag = reportObject;

            List<string> detailValues = new List<string>();
            for (int x = 0; x < this.Columns.Count; x++)
                detailValues.Add("(unknown)");

            foreach (PropertyDescriptor prop in this.Properties) {
                MmcListViewColumn column = this.Columns.Cast<MmcListViewColumn>()
                    .Where(c => c.Title.Equals(prop.DisplayName, StringComparison.OrdinalIgnoreCase))
                    .FirstOrDefault();
                if (column == null)
                    continue;

                int columnIndex = this.Columns.IndexOf(column);

                object objValue = prop.GetValue(reportObject);
                string value;

                if (prop.Converter.CanConvertTo(typeof(string))) {
                    value = prop.Converter.ConvertToString(objValue);
                } else {
                    if (objValue == null) {
                        value = null;
                    } else {
                        value = objValue.ToString();
                    }
                }

                if (string.IsNullOrWhiteSpace(value)) {
                    value = "(not set)";
                }

                detailValues[columnIndex] = value;
                //if (prop.Name.Equals("Name", StringComparison.OrdinalIgnoreCase)) {
                //}
            }

            node.DisplayName = detailValues[0];

            detailValues.RemoveAt(0);

            for (int x = 0; x < detailValues.Count; x++) {
                while (node.SubItemDisplayNames.Count <= x) {
                    node.SubItemDisplayNames.Add("");
                }

                if (!node.SubItemDisplayNames[x].Equals(detailValues[x])) {
                    node.SubItemDisplayNames[x] = detailValues[x];
                }
            }

            while (node.SubItemDisplayNames.Count > detailValues.Count) {
                node.SubItemDisplayNames.RemoveAt(node.SubItemDisplayNames.Count - 1);
            }
        }
        protected virtual object GetDescriptionObject() {
            return null;
        }

        public void Refresh(T obj) {
            if (this.Options.HasFlag(MmcListViewOptions.ExcludeScopeNodes)) {
                ResultNode node = this.ResultNodes.Cast<ResultNode>().Where(n => n.Tag == (object)obj).FirstOrDefault();
                if (node == null) {
                    return;
                }

                PopulateResultNode(node, obj);
            } else {
                ScopeNode node = this.ScopeNode.Children.Cast<ScopeNode>().Where(n => n.Tag == (object)obj).FirstOrDefault();
                if (node == null) {
                    return;
                }

                PopulateResultNode(node, obj);
            }
        }
        public void Refresh() {
            var reportObjects = this.ListReportObjects().ToList();

            if (this.Options.HasFlag(MmcListViewOptions.ExcludeScopeNodes)) {
                List<ResultNode> nodes = new List<ResultNode>();

                var objectsWithNodes = reportObjects
                    .Select(o => new {
                        Object = o,
                        Node = this.ResultNodes
                            .Cast<ResultNode>()
                            .Where(n => this.ReportObjectComparer.Equals((T)n.Tag, o))
                            .FirstOrDefault()
                    });

                var nodesWithObjects = this.ResultNodes
                    .Cast<ResultNode>()
                    .Select(n => new {
                        Object = reportObjects
                            .Where(o => this.ReportObjectComparer.Equals((T)n.Tag, o))
                            .FirstOrDefault(),
                        Node = n
                    });

                var nodesAndObjects = objectsWithNodes.Concat(nodesWithObjects).ToList();

                foreach (var nodeAndObj in nodesAndObjects) {
                    ResultNode updateResultNode;

                    if (nodeAndObj.Node != null && nodeAndObj.Object == null) {
                        this.ResultNodes.Remove(nodeAndObj.Node);
                        continue;
                    }

                    if (nodeAndObj.Node == null) {
                        updateResultNode = CreateResultNode(nodeAndObj.Object);
                        nodes.Add(updateResultNode);
                    } else {
                        updateResultNode = nodeAndObj.Node;
                    }

                    this.PopulateResultNode(updateResultNode, nodeAndObj.Object);
                }

                if (nodes.Count > 0) {
                    this.ResultNodes.AddRange(nodes.ToArray());
                }
            } else {
                foreach (T obj in reportObjects) {
                    Node node = this.ScopeNode.Children.Cast<Node>().Where(n => n.Tag == (object)obj).FirstOrDefault();
                    if (node == null)
                        continue;
                    this.PopulateResultNode(node, obj);
                }
            }
        }

        protected override void OnInitialize(AsyncStatus status) {
            base.OnInitialize(status);

            this.Mode = MmcListViewMode.Report;

            object descriptionObj = this.GetDescriptionObject();
            if (descriptionObj == null) {
                this.Properties = TypeDescriptor.GetProperties(typeof(T));
            } else {
                this.Properties = TypeDescriptor.GetProperties(descriptionObj);
            }

            foreach (PropertyDescriptor prop in this.Properties.Cast<PropertyDescriptor>().OrderBy(d => d.DisplayName)) {
                if (!prop.IsBrowsable || prop.DisplayName.Equals("Name", StringComparison.OrdinalIgnoreCase))
                    continue;

                MmcListViewColumn column = new MmcListViewColumn() {
                    Title = prop.DisplayName
                };

                this.Columns.Add(column);
            }

            this.Refresh();
        }

        protected override void OnSelectionChanged(SyncStatus status) {
            base.OnSelectionChanged(status);

            if (this.SelectedNodes.Count == 0) {
                this.SelectionData.Clear();
            } else {
                this.SelectionData.Update(this.SelectedNodes[0].Tag, false, null, null);
            }
        }
        protected override void OnRefresh(AsyncStatus status) {
            base.OnRefresh(status);

            this.Refresh();
        }

        protected override void OnShow() {
            base.OnShow();

            if (ShouldRefreshPeriodically) {
                _updateTimer = new Timer(1000.0);
                _updateTimer.Elapsed += new ElapsedEventHandler(UpdateTimerElapsed);
                _updateTimer.Start();
            }
        }
        protected override void OnHide() {
            base.OnHide();

            if (_updateTimer != null) {
                _updateTimer.Dispose();
                _updateTimer = null;
            }
        }

        private void UpdateTimerElapsed(object sender, ElapsedEventArgs e) {
            this.SnapIn.Invoke(new System.Action(() => {
                this.Refresh();
            }));
        }

    }
}
